use std::collections::HashMap;

use godot::prelude::*;

use crate::variant_ext::TryAsSingle;

pub struct PreProcessor {
    builtins: HashMap<StringName, fn(&VariantArray) -> Result<Variant, ()>>,
    macros: HashMap<StringName, Callable>,
}

impl PreProcessor {
    pub fn new() -> Self {
        let mut builtins: HashMap<StringName, fn(&VariantArray) -> Result<Variant, ()>> =
            HashMap::new();

        builtins.insert("+".into(), add);
        builtins.insert("-".into(), sub);
        builtins.insert("*".into(), mul);
        builtins.insert("/".into(), div);
        builtins.insert("vec".into(), vec);
        builtins.insert("dict".into(), dict);

        Self {
            builtins,
            macros: HashMap::new(),
        }
    }

    pub fn with_macros(macros: Dictionary) -> Self {
        let mut pp = Self::new();

        for (k, v) in macros.iter_shared() {
            match (k.try_to::<StringName>(), v.try_to::<Callable>()) {
                (Ok(name), Ok(m)) => {
                    pp.macros.insert(name, m);
                }
                _ => {}
            }
        }

        pp
    }

    pub fn process(&self, ast: Variant) -> Result<Variant, ()> {
        match ast.get_type() {
            VariantType::ARRAY => {
                let mut processed = VariantArray::new();

                let mut compound = ast.to::<VariantArray>();
                for v in compound.iter_shared() {
                    processed.push(self.process(v)?);
                }

                for i in 0..processed.len() {
                    compound.set(i, processed.get(i).unwrap());
                }

                match processed.pop_front() {
                    Some(v) if v.get_type() == VariantType::STRING_NAME => {
                        let name = v.to::<StringName>();
                        if let Some(f) = self.builtins.get(&name) {
                            match f(&processed) {
                                Ok(v) => Ok(v),
                                _ => Ok(compound.to_variant()),
                            }
                        } else if let Some(m) = self.macros.get(&name) {
                            Ok(m.callv(processed))
                        } else {
                            Ok(compound.to_variant())
                        }
                    }
                    _ => Ok(compound.to_variant()),
                }
            }
            _ => Ok(ast),
        }
    }
}

fn add(vars: &VariantArray) -> Result<Variant, ()> {
    let mut iter = vars.iter_shared();
    if let Some(mut n) = iter.next() {
        for v in iter {
            n = n.evaluate(&v, VariantOperator::ADD).ok_or(())?
        }
        Ok(n)
    } else {
        Err(())
    }
}

fn sub(vars: &VariantArray) -> Result<Variant, ()> {
    if vars.len() == 1 {
        let v = vars.get(0).unwrap();
        return Ok(v
            .evaluate(&Variant::nil(), VariantOperator::NEGATE)
            .ok_or(())?);
    }
    let mut iter = vars.iter_shared();
    if let Some(mut n) = iter.next() {
        for v in iter {
            n = n.evaluate(&v, VariantOperator::SUBTRACT).ok_or(())?
        }
        Ok(n)
    } else {
        Err(())
    }
}

fn mul(vars: &VariantArray) -> Result<Variant, ()> {
    let mut iter = vars.iter_shared();
    if let Some(mut n) = iter.next() {
        for v in iter {
            n = n.evaluate(&v, VariantOperator::MULTIPLY).ok_or(())?
        }
        Ok(n)
    } else {
        Err(())
    }
}

fn div(vars: &VariantArray) -> Result<Variant, ()> {
    let mut iter = vars.iter_shared();
    if let Some(mut n) = iter.next() {
        for v in iter {
            n = n.evaluate(&v, VariantOperator::DIVIDE).ok_or(())?
        }
        Ok(n)
    } else {
        Err(())
    }
}

fn vec(vars: &VariantArray) -> Result<Variant, ()> {
    match vars.len() {
        2 => {
            let x = vars.get(0).unwrap().try_as_single()?;
            let y = vars.get(1).unwrap().try_as_single()?;

            Ok(Vector2::new(x, y).to_variant())
        }
        3 => {
            let x = vars.get(0).unwrap().try_as_single()?;
            let y = vars.get(1).unwrap().try_as_single()?;
            let z = vars.get(2).unwrap().try_as_single()?;

            Ok(Vector3::new(x, y, z).to_variant())
        }
        4 => {
            let x = vars.get(0).unwrap().try_as_single()?;
            let y = vars.get(1).unwrap().try_as_single()?;
            let z = vars.get(2).unwrap().try_as_single()?;
            let w = vars.get(3).unwrap().try_as_single()?;

            Ok(Vector4::new(x, y, z, w).to_variant())
        }
        _ => Err(()),
    }
}

fn dict(vars: &VariantArray) -> Result<Variant, ()> {
    let mut dict = Dictionary::new();
    for t in vars.iter_shared() {
        if let Ok(tuple) = t.try_to::<VariantArray>() {
            match (tuple.get(0), tuple.get(1)) {
                (Some(key), Some(val)) => {
                    let _ = dict.insert(key, val);
                }
                _ => continue,
            }
        }
    }
    Ok(dict.to_variant())
}